using DynamicLogicExpression.Extensions;
using System.Linq.Expressions;
using System.Reflection;

namespace DynamicLogicExpression.Core;

public sealed class LogicOperator<T>
{
    private static readonly IDictionary<PropertyNavigation, (Expression leftMember, Expression rightMember)> _memberCaches =
        new Dictionary<PropertyNavigation, (Expression, Expression)>();

    private static readonly IDictionary<(PropertyNavigation, Func<Expression, Expression, BinaryExpression>), Func<T, string, bool>> _delegateCaches =
        new Dictionary<(PropertyNavigation, Func<Expression, Expression, BinaryExpression>), Func<T, string, bool>>();

    private readonly InternalLogicOperator _internalLogic;

    public static LogicOperator<T> LessThan { get; private set; }
    public static LogicOperator<T> LessThanOrEqual { get; private set; }
    public static LogicOperator<T> Equal { get; private set; }
    public static LogicOperator<T> GreaterThan { get; private set; }
    public static LogicOperator<T> GreaterThanOrEqual { get; private set; }
    public static LogicOperator<T> NotEqual { get; private set; }

    static LogicOperator()
    {
        LessThan = new LogicOperator<T>(InternalLogicOperator.LessThan);
        LessThanOrEqual = new LogicOperator<T>(InternalLogicOperator.LessThanOrEqual);
        Equal = new LogicOperator<T>(InternalLogicOperator.Equal);
        GreaterThan = new LogicOperator<T>(InternalLogicOperator.GreaterThan);
        GreaterThanOrEqual = new LogicOperator<T>(InternalLogicOperator.GreaterThanOrEqual);
        NotEqual = new LogicOperator<T>(InternalLogicOperator.NotEqual);
    }

    private LogicOperator(InternalLogicOperator @operator)
    {
        _internalLogic = @operator;
    }

    public static LogicOperator<T> GetOperator(string @operator)
    {
        return @operator switch
        {
            "<" => LogicOperator<T>.LessThan,
            "<=" => LogicOperator<T>.LessThanOrEqual,
            "=" => LogicOperator<T>.Equal,
            ">" => LogicOperator<T>.GreaterThan,
            ">=" => LogicOperator<T>.GreaterThanOrEqual,
            "!=" => LogicOperator<T>.NotEqual,
            _ => throw new NotImplementedException(),
        };
    }

    public Func<T, string, bool> BuildExpression(PropertyNavigation propNav)
    {
        return LogicOperatorBuilder.Build(_internalLogic, propNav);
    }

    private class LogicOperatorBuilder
    {
        private static MethodInfo ChangeTypeMethod => typeof(Convert).GetMethod(
                "ChangeType",
                BindingFlags.Public | BindingFlags.Static,
                new[] { typeof(object), typeof(Type) })!;

        private static (ParameterExpression leftArgs, ParameterExpression rightArgs) _operation
            = (Expression.Parameter(typeof(T), "left"), Expression.Parameter(typeof(string), "right"));

        public static Func<T, string, bool> Build(InternalLogicOperator @operator, PropertyNavigation propNav)
        {
            return @operator switch
            {
                InternalLogicOperator.LessThan => BuildExpression(Expression.LessThan, propNav),
                InternalLogicOperator.LessThanOrEqual => BuildExpression(Expression.LessThanOrEqual, propNav),
                InternalLogicOperator.Equal => BuildExpression(Expression.Equal, propNav),
                InternalLogicOperator.GreaterThan => BuildExpression(Expression.GreaterThan, propNav),
                InternalLogicOperator.GreaterThanOrEqual => BuildExpression(Expression.GreaterThanOrEqual, propNav),
                InternalLogicOperator.NotEqual => BuildExpression(Expression.NotEqual, propNav),
                _ => throw new NotImplementedException(),
            };
        }

        #region InnerProcess
        private static Func<T, string, bool> BuildExpression(Func<Expression, Expression, BinaryExpression> factor, PropertyNavigation propNav)
        {
            if (_delegateCaches.TryGetValue((propNav, factor), out var func))
            {
                return func;
            }

            if (_memberCaches.TryGetValue(propNav, out var members))
            {
                var compilingDelegateFromCache = Expression.Lambda<Func<T, string, bool>>(
                    factor(members.leftMember, members.rightMember),
                    new ParameterExpression[] { _operation.leftArgs, _operation.rightArgs }).Compile();

                _delegateCaches.Add((propNav, factor), compilingDelegateFromCache);

                return compilingDelegateFromCache;
            }

            var (leftMember, rightMember) = CreateOpeartionExpressionBody(propNav);

            var compiled = Expression.Lambda<Func<T, string, bool>>(factor(leftMember, rightMember), new ParameterExpression[] { _operation.leftArgs, _operation.rightArgs }).Compile();

            _memberCaches.Add(propNav, (leftMember, rightMember));
            _delegateCaches.Add((propNav, factor), compiled);

            return compiled;
        }

        private static (Expression leftMember, Expression rightMember) CreateOpeartionExpressionBody(PropertyNavigation propNav)
        {
            if (propNav.SubNavigation is null)
            {
                var leftMember = Expression.Property(_operation.leftArgs, propNav.Navigation);

                var rightMember = CreateUnaryExpression(false, _operation.rightArgs, propNav);

                return (leftMember, rightMember);
            }
            else
            {
                var leftMember = Expression.Property(_operation.leftArgs, propNav.Navigation).GetNestedMemberExpreesion(propNav);

                var rightMember = CreateUnaryExpression(true, _operation.rightArgs, propNav);

                return (leftMember, rightMember);
            }
        }

        private static UnaryExpression CreateUnaryExpression(bool hasSubNav, ParameterExpression rightParameter, PropertyNavigation propNav)
        {
            if (hasSubNav)
            {
                var leftValueType = propNav.GetDeepestValueType();
                var rightValueType = Expression.Constant(leftValueType, typeof(Type));

                return Expression.Convert(Expression.Call(ChangeTypeMethod, rightParameter, rightValueType), leftValueType);
            }
            else
            {
                var rightValueType = Expression.Constant(propNav.PropertyInfo.PropertyType, typeof(Type));

                return Expression.Convert(Expression.Call(ChangeTypeMethod, rightParameter, rightValueType), propNav.PropertyInfo.PropertyType);
            }
        }
        #endregion
    }

    private enum InternalLogicOperator
    {
        LessThan,
        LessThanOrEqual,
        Equal,
        GreaterThan,
        GreaterThanOrEqual,
        NotEqual
    }
}
